function convert(input) { alert('Error. Contact us if this keeps happening.'); document.getElementById('modal-contact').classList.add('show'); } window.ctMaxUploadFiles = 1; var _psState = { buffer: null, fileName: 'audio', previewSource: null, previewCtx: null }; var _psSoundTouchModule = null; function _psLoadSoundTouch() { if (_psSoundTouchModule) return Promise.resolve(_psSoundTouchModule); return import('https://cdn.jsdelivr.net/npm/soundtouchjs@0.3.0/dist/soundtouch.js').then(function(mod) { _psSoundTouchModule = mod; return mod; }); } function processFile(blob, fileName) { _psState.fileName = fileName || 'audio'; $('#file-drop-zone').addClass('collapsedDropZone'); (function(){ var $b=$('#file-drop-zone .upload-file-button'); var l=$b.data('change-label'); if(l) $b.text(l); })(); var ws = document.getElementById('ps-workspace'); if (ws) ws.style.display = 'block'; var audio = document.getElementById('ps-audio'); if (audio) audio.src = URL.createObjectURL(blob); var reader = new FileReader(); reader.onload = function(e) { var AC = window.AudioContext || window.webkitAudioContext; if (!AC) { alert('Web Audio is not supported in this browser.'); return; } var ac = new AC(); ac.decodeAudioData(e.target.result.slice(0), function(buf) { _psState.buffer = buf; }, function() { alert('Could not decode this audio file. Try MP3, WAV, M4A, OGG, or FLAC.'); }); }; reader.readAsArrayBuffer(blob); } function _psRender(pitchSemitones, speed) { if (!_psState.buffer) return Promise.reject(new Error('No audio loaded.')); return _psLoadSoundTouch().then(function(mod) { var SoundTouch = mod.SoundTouch; var SimpleFilter = mod.SimpleFilter; var WebAudioBufferSource = mod.WebAudioBufferSource; if (!SoundTouch || !SimpleFilter || !WebAudioBufferSource) { throw new Error('SoundTouch API not available.'); } var srcBuf = _psState.buffer; var sr = srcBuf.sampleRate; var totalSamples = srcBuf.length; var st = new SoundTouch(); st.tempo = speed; st.pitchSemitones = pitchSemitones; var bufSource = new WebAudioBufferSource(srcBuf); var filter = new SimpleFilter(bufSource, st); var outFrames = Math.ceil(totalSamples / Math.max(speed, 0.05)) + 4096; var stereo = new Float32Array(outFrames * 2); var written = 0; var chunk = 8192; var tmp = new Float32Array(chunk * 2); for (;;) { var got = filter.extract(tmp, chunk); if (got === 0) break; if (written + got > outFrames) { var grown = new Float32Array((written + got + chunk) * 2); grown.set(stereo); stereo = grown; outFrames = grown.length / 2; } for (var i = 0; i < got * 2; i++) stereo[written * 2 + i] = tmp[i]; written += got; } var outBuf = new AudioBuffer({ length: written, sampleRate: sr, numberOfChannels: 2 }); var outL = outBuf.getChannelData(0); var outR = outBuf.getChannelData(1); for (var i = 0; i < written; i++) { outL[i] = stereo[i * 2]; outR[i] = stereo[i * 2 + 1]; } return outBuf; }); } function _psEncodeWav(buffer) { var ch = buffer.numberOfChannels; var sr = buffer.sampleRate; var samples = buffer.length * ch; var size = 44 + samples * 2; var ab = new ArrayBuffer(size); var view = new DataView(ab); var ws = function(o, s) { for (var i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i)); }; ws(0, 'RIFF'); view.setUint32(4, size - 8, true); ws(8, 'WAVE'); ws(12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, ch, true); view.setUint32(24, sr, true); view.setUint32(28, sr * ch * 2, true); view.setUint16(32, ch * 2, true); view.setUint16(34, 16, true); ws(36, 'data'); view.setUint32(40, samples * 2, true); var offset = 44; var data = []; for (var c = 0; c < ch; c++) data.push(buffer.getChannelData(c)); for (var i = 0; i < buffer.length; i++) { for (var c2 = 0; c2 < ch; c2++) { var s = Math.max(-1, Math.min(1, data[c2][i])); view.setInt16(offset, s < 0 ? s * 32768 : s * 32767, true); offset += 2; } } return new Blob([ab], { type: 'audio/wav' }); } function _psEncodeMp3(buffer, bitrate) { return loadScriptPromise('https://cdn.jsdelivr.net/npm/lamejs@1.2.1/lame.min.js').then(function() { if (typeof lamejs === 'undefined') throw new Error('MP3 encoder failed to load'); var ch = Math.min(2, buffer.numberOfChannels); var sr = buffer.sampleRate; var enc = new lamejs.Mp3Encoder(ch, sr, bitrate); var left = buffer.getChannelData(0); var right = ch > 1 ? buffer.getChannelData(1) : null; var samples = buffer.length; var li = new Int16Array(samples); var ri = right ? new Int16Array(samples) : null; for (var i = 0; i < samples; i++) { var l = Math.max(-1, Math.min(1, left[i])); li[i] = l < 0 ? l * 32768 : l * 32767; if (ri) { var rv = Math.max(-1, Math.min(1, right[i])); ri[i] = rv < 0 ? rv * 32768 : rv * 32767; } } var bs = 1152, chunks = []; for (var i = 0; i < samples; i += bs) { var lc = li.subarray(i, i + bs); var out = ri ? enc.encodeBuffer(lc, ri.subarray(i, i + bs)) : enc.encodeBuffer(lc); if (out.length) chunks.push(new Int8Array(out)); } var f = enc.flush(); if (f.length) chunks.push(new Int8Array(f)); return new Blob(chunks, { type: 'audio/mpeg' }); }); } function _psReadParams() { var pitch = parseFloat(document.getElementById('ps-pitch').value); var speed = parseFloat(document.getElementById('ps-speed').value); if (!isFinite(pitch)) pitch = 0; if (!isFinite(speed) || speed <= 0.05) speed = 1; return { pitch: pitch, speed: speed }; } function _psApply() { if (!_psState.buffer) { alert('Drop an audio file first.'); return; } var p = _psReadParams(); var format = document.getElementById('ps-format').value; var status = document.getElementById('ps-status'); var btn = document.getElementById('ps-apply'); if (btn) btn.disabled = true; if (status) status.textContent = 'Rendering... (loads SoundTouch on first run)'; _psRender(p.pitch, p.speed).then(function(buf) { var base = (_psState.fileName || 'audio').replace(/\.[^.]+$/, ''); if (format === 'wav') { var blob = _psEncodeWav(buf); add_file_output(URL.createObjectURL(blob), base + '-pitchspeed.wav'); if (status) status.textContent = 'Done.'; if (btn) btn.disabled = false; } else { if (status) status.textContent = 'Encoding MP3...'; _psEncodeMp3(buf, 192).then(function(blob) { add_file_output(URL.createObjectURL(blob), base + '-pitchspeed.mp3'); if (status) status.textContent = 'Done.'; if (btn) btn.disabled = false; }).catch(function(err) { if (status) status.textContent = ''; if (btn) btn.disabled = false; alert('MP3 encoding failed: ' + (err && err.message || err)); }); } }).catch(function(err) { if (status) status.textContent = ''; if (btn) btn.disabled = false; alert('Could not process audio: ' + (err && err.message || err)); }); } function _psPreview() { if (!_psState.buffer) { alert('Drop an audio file first.'); return; } if (_psState.previewSource) { try { _psState.previewSource.stop(); } catch (e) {} _psState.previewSource = null; return; } var p = _psReadParams(); var status = document.getElementById('ps-status'); if (status) status.textContent = 'Rendering preview...'; _psRender(p.pitch, p.speed).then(function(buf) { var AC = window.AudioContext || window.webkitAudioContext; var ac = new AC(); _psState.previewCtx = ac; var src = ac.createBufferSource(); src.buffer = buf; src.connect(ac.destination); src.start(); _psState.previewSource = src; src.onended = function() { _psState.previewSource = null; }; if (status) status.textContent = 'Playing preview...'; }).catch(function(err) { if (status) status.textContent = ''; alert('Could not preview: ' + (err && err.message || err)); }); } function _psInit() { var pitch = document.getElementById('ps-pitch'); var pitchLabel = document.getElementById('ps-pitch-label'); if (pitch && pitchLabel) { pitch.addEventListener('input', function() { var v = parseFloat(pitch.value); pitchLabel.textContent = (v > 0 ? '+' : '') + v.toFixed(1) + ' st'; }); } var speed = document.getElementById('ps-speed'); var speedLabel = document.getElementById('ps-speed-label'); if (speed && speedLabel) { speed.addEventListener('input', function() { var v = parseFloat(speed.value); speedLabel.textContent = v.toFixed(2) + 'x'; }); } var pr = document.getElementById('ps-pitch-reset'); if (pr) pr.addEventListener('click', function() { pitch.value = '0'; pitch.dispatchEvent(new Event('input')); }); var sr = document.getElementById('ps-speed-reset'); if (sr) sr.addEventListener('click', function() { speed.value = '1'; speed.dispatchEvent(new Event('input')); }); var apply = document.getElementById('ps-apply'); if (apply) apply.addEventListener('click', _psApply); var prev = document.getElementById('ps-preview'); if (prev) prev.addEventListener('click', _psPreview); } document.addEventListener('DOMContentLoaded', _psInit); if (document.readyState !== 'loading') _psInit(); var _loadedScripts = {}; function loadScriptPromise(url) { if (_loadedScripts[url]) return _loadedScripts[url]; _loadedScripts[url] = new Promise(function (resolve, reject) { var s = document.createElement('script'); s.src = url; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); return _loadedScripts[url]; } function replaceAll(find, replace, str) { return str.replace(new RegExp(find, 'g'), replace); } function beautify(str) { var result = ''; var length = str.length; var i = 0; var braceCountLeft = 0; var braceCountRight = 0; var withinQuotes = false; while (i < length) { var c = str[i]; if (c == '"' && (i == 0 || c[i - 1] != '\\')) { // non-escaped quotes withinQuotes = !withinQuotes; } if (!withinQuotes && (c == '}' || c == '{' || c == ',')) { console.log('Start####' + result); // look back and remove carriage returns and whitespace that are already there var resultIndex = result.length - 1; while (resultIndex >= 0 && (result[resultIndex] == ' ' || result[resultIndex] == '\r' || result[resultIndex] == '\n' || result[resultIndex] == '\t')) { resultIndex = resultIndex - 1; result = result.substr(0, resultIndex + 1); console.log('char ' + result[resultIndex] + '-----' + result + 'zzz ' + result.length + ' ' + resultIndex); } if (c == '{') { braceCountLeft++; result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } else if (c == '}') { braceCountRight++; // precede with carriage return result += '\r' + GetTabs(braceCountLeft - braceCountRight) + c; } else if (c == ',') { result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } var nextChar = ''; // advance through whitespace and remove carriage returns that are already there while (i < length && (str[i + 1] == ' ' || str[i + 1] == '\r' || str[i + 1] == '\n' || str[i + 1] == '\t')) { i++; } } else { result += str[i]; } i++; } return result; } function GetTabs(count) { var result = ''; for (var i = 0; i < count; i++) { result += ' '; } return result; }